﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Geometries;
using YJKGridTopologyCal.Geometries.Utilities;
using YJKGridTopologyCal.Index.Strtree;

namespace YJKGridTopologyCal.Operation.Union
{
	public class CascadedPolygonUnion
	{
		public static IGeometry Union(ICollection<IGeometry> polys)
		{
			return new CascadedPolygonUnion(polys).Union();
		}

		public CascadedPolygonUnion(ICollection<IGeometry> polys)
		{
			this._inputPolys = (polys ?? new List<IGeometry>());
		}

		private IGeometryFactory Factory()
		{
			IEnumerator<IGeometry> enumerator = this._inputPolys.GetEnumerator();
			enumerator.MoveNext();
			return enumerator.Current.Factory;
		}

		public IGeometry Union()
		{
			if (this._inputPolys == null)
			{
				throw new InvalidOperationException("Union() method cannot be called twice");
			}
			if (this._inputPolys.Count == 0)
			{
				return null;
			}
			this._geomFactory = this.Factory();
			STRtree<object> strtree = new STRtree<object>(4);
			foreach (IGeometry geometry in this._inputPolys)
			{
				strtree.Insert(geometry.EnvelopeInternal, geometry);
			}
			this._inputPolys = null;
			IList<object> geomTree = strtree.ItemsTree();
			return this.UnionTree(geomTree);
		}

		private IGeometry UnionTree(IList<object> geomTree)
		{
			IList<IGeometry> geoms = this.ReduceToGeometries(geomTree);
			return this.BinaryUnion(geoms);
		}

		private IGeometry RepeatedUnion(IEnumerable<IGeometry> geoms)
		{
			IGeometry geometry = null;
			foreach (IGeometry geometry2 in geoms)
			{
				if (geometry == null)
				{
					geometry = (IGeometry)geometry2.Clone();
				}
				else
				{
					geometry = geometry.Union(geometry2);
				}
			}
			return geometry;
		}

		private IGeometry BufferUnion(ICollection<IGeometry> geoms)
		{
			return this.Factory().BuildGeometry(geoms).Buffer(0.0);
		}

		private IGeometry BufferUnion(IGeometry g0, IGeometry g1)
		{
			return g0.Factory.CreateGeometryCollection(new IGeometry[]
			{
				g0,
				g1
			}).Buffer(0.0);
		}

		private IGeometry BinaryUnion(IList<IGeometry> geoms)
		{
			return this.BinaryUnion(geoms, 0, geoms.Count, false);
		}

		private IGeometry BinaryUnion(IList<IGeometry> geoms, int start, int end, bool multiple = false)
		{
			IGeometry g;
			if (end - start <= 1)
			{
				g = CascadedPolygonUnion.GetGeometry(geoms, start);
				return this.UnionSafe(g, null);
			}
			if (end - start == 2)
			{
				return this.UnionSafe(CascadedPolygonUnion.GetGeometry(geoms, start), CascadedPolygonUnion.GetGeometry(geoms, start + 1));
			}
			int num = (end + start) / 2;
			g = this.BinaryUnion(geoms, start, num, false);
			IGeometry g2 = this.BinaryUnion(geoms, num, end, false);
			return this.UnionSafe(g, g2);
		}

		private static IGeometry GetGeometry(IList<IGeometry> list, int index)
		{
			if (index >= list.Count)
			{
				return null;
			}
			return list[index];
		}

		private IList<IGeometry> ReduceToGeometries(IList<object> geomTree)
		{
			IList<IGeometry> list = new List<IGeometry>();
			foreach (object obj in geomTree)
			{
				IGeometry item = null;
				if (obj is IList<object>)
				{
					item = this.UnionTree((IList<object>)obj);
				}
				else if (obj is IGeometry)
				{
					item = (IGeometry)obj;
				}
				list.Add(item);
			}
			return list;
		}

		private IGeometry UnionSafe(IGeometry g0, IGeometry g1)
		{
			if (g0 == null && g1 == null)
			{
				return null;
			}
			if (g0 == null)
			{
				return (IGeometry)g1.Clone();
			}
			if (g1 == null)
			{
				return (IGeometry)g0.Clone();
			}
			return this.UnionOptimized(g0, g1);
		}

		private IGeometry UnionOptimized(IGeometry g0, IGeometry g1)
		{
			Envelope envelopeInternal = g0.EnvelopeInternal;
			Envelope envelopeInternal2 = g1.EnvelopeInternal;
			if (!envelopeInternal.Intersects(envelopeInternal2))
			{
				return GeometryCombiner.Combine(g0, g1);
			}
			if (g0.NumGeometries <= 1 && g1.NumGeometries <= 1)
			{
				return CascadedPolygonUnion.UnionActual(g0, g1);
			}
			Envelope common = envelopeInternal.Intersection(envelopeInternal2);
			return this.UnionUsingEnvelopeIntersection(g0, g1, common);
		}

		private IGeometry UnionUsingEnvelopeIntersection(IGeometry g0, IGeometry g1, Envelope common)
		{
			List<IGeometry> list = new List<IGeometry>();
			IGeometry g2 = this.ExtractByEnvelope(common, g0, list);
			IGeometry g3 = this.ExtractByEnvelope(common, g1, list);
			IGeometry item = CascadedPolygonUnion.UnionActual(g2, g3);
			list.Add(item);
			return GeometryCombiner.Combine(list);
		}

		private IGeometry ExtractByEnvelope(Envelope env, IGeometry geom, IList<IGeometry> disjointGeoms)
		{
			List<IGeometry> list = new List<IGeometry>();
			for (int i = 0; i < geom.NumGeometries; i++)
			{
				IGeometry geometryN = geom.GetGeometryN(i);
				if (geometryN.EnvelopeInternal.Intersects(env))
				{
					list.Add(geometryN);
				}
				else
				{
					disjointGeoms.Add(geometryN);
				}
			}
			return this._geomFactory.BuildGeometry(list);
		}

		private static IGeometry UnionActual(IGeometry g0, IGeometry g1)
		{
			return CascadedPolygonUnion.RestrictToPolygons(g0.Union(g1));
		}

		private static IGeometry RestrictToPolygons(IGeometry g)
		{
			if (g is IPolygonal)
			{
				return g;
			}
			IList<IGeometry> polygons = PolygonExtracter.GetPolygons(g);
			if (polygons.Count == 1)
			{
				return polygons[0];
			}
			IPolygon[] polygons2 = GeometryFactory.ToPolygonArray(polygons);
			return g.Factory.CreateMultiPolygon(polygons2);
		}

		private ICollection<IGeometry> _inputPolys;

		private IGeometryFactory _geomFactory;

		private const int StrtreeNodeCapacity = 4;
	}
}
